﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Needle.Core.Injection;
using System.Threading;
using Needle.Utils.Extensions;
using System.IO;

namespace Needle.Simulation
{
    public class Simulation  : IDisposable
    {

        #region private fields

        private List<Task> _TaskQueue;
        private List<Process> _ProcessList;
        private Dictionary<string, List<Task>> _EventListeners;
        private TextWriter _Log;
        #endregion

        #region Constructor

        public Simulation()
        {
            _TaskQueue = new List<Task>();
            _ProcessList = new List<Process>();
            _EventListeners = new Dictionary<string, List<Task>>();
            if (File.Exists("log.txt"))
                File.Delete("log.txt");
            _Log = new StreamWriter(new BufferedStream(File.OpenWrite("log.txt"), 256.KiloByte()), Encoding.UTF8);
        }

        #endregion

        #region Properties

        [Inject("MaxRunTime")]
        public TimeSpan MaxRunTime { get; set; }
        public TimeSpan CurrentTime { get; private set; }
        public bool DebugOutput { get; set; }

        #endregion

        #region Public Methods

        public void Run()
        {
            CurrentTime = TimeSpan.Zero;
            int count = _TaskQueue.Count;
            while (CurrentTime < MaxRunTime && count > 0)
            {

                Task t = _TaskQueue[count - 1];
                _TaskQueue.RemoveAt(count - 1);
                CurrentTime = t.Time;
                if (DebugOutput) LogMessage(t.ProcessId, "~~~> executing {0}".FormattedWith(t));
                switch (t.Type)
                {
                    case TaskType.RaiseEvent:
                        RaiseEvent(t.EventName);
                        break;
                    case TaskType.WaitEvent:
                    case TaskType.Delay:
                    case TaskType.Suspend:
                    default:
                        break;
                }
                t.Execute();
                if (DebugOutput) LogMessage(t.ProcessId, "•••> finished  {0}".FormattedWith(t));
                count = _TaskQueue.Count;
            }
        }

        public void StartProcess(Process process)
        {
            if (!_ProcessList.Contains(process))
            {
                _ProcessList.Add(process);
                process.TaskScheduled += ScheduleTask;
                process.ProcessSpawned += SpawnProcess;
                process.MessageLogged += LogMessage;
                process.Schedule();
            }
        }

        #endregion

        #region private Methods

        private void ScheduleTask(int processId, Task t)
        {
            ScheduleTask(t);
            if (DebugOutput) LogMessage(processId, "+++> scheduled {0}".FormattedWith(t));
        }

        private void ScheduleTask(Task task)
        {
            switch (task.Type)
            {
                case TaskType.RaiseEvent:
                    task.Time = CurrentTime;
                    _TaskQueue.Add(task);
                    break;
                case TaskType.WaitEvent:
                    AddEventListener(task);
                    break;
                case TaskType.Delay:
                    task.Time = CurrentTime + task.DelayAmount;
                    _TaskQueue.Add(task);

                    _TaskQueue.Sort((a, b) => b.Time.CompareTo(a.Time));/* reverse sort */
                    break;
                case TaskType.Suspend:
                    break;
            }
        }

        private void SpawnProcess(int parentId, Process child)
        {
            StartProcess(child);
            if (DebugOutput) LogMessage(parentId, "•••> p# {0} spawned child p# {1}".FormattedWith(parentId, child.Id));
        }

        private void LogMessage(int processId, string message)
        {

            _Log.WriteLine("[{0,8} p#{1,2}] {2}", CurrentTime, processId, message);
        }

        private void AddEventListener(Task listener)
        {
            if (listener.Type != TaskType.WaitEvent)
                throw new ArgumentException();
            List<Task> list = null;
            if (!_EventListeners.TryGetValue(listener.EventName, out list))
            {
                list = new List<Task>();
                _EventListeners.Add(listener.EventName, list);
            }
            list.Add(listener);
        }

        private void RaiseEvent(string eventName)
        {
            List<Task> list = null;
            if (_EventListeners.TryGetValue(eventName, out list))
            {
                _EventListeners[eventName] = new List<Task>();
                list.ForEach(t =>
                    {
                        if (DebugOutput) LogMessage(t.ProcessId, "~~~> executing {0}".FormattedWith(t));
                        t.Execute();
                        if (DebugOutput) LogMessage(t.ProcessId, "•••> finished  {0}".FormattedWith(t));
                    });
            }
        }

        #endregion

        #region IDisposable Members

        public void Dispose()
        {
            _Log.Close();
        }

        #endregion
    }
}
